modify position filter algorithm. (#373)
authortsteven4 <13596209+tsteven4@users.noreply.github.com>
Mon, 8 Jul 2019 20:54:46 +0000 (14:54 -0600)
committerGitHub <noreply@github.com>
Mon, 8 Jul 2019 20:54:46 +0000 (14:54 -0600)
For routes and tracks, the algorithm is modified to
only remove consecutive points until the distance limit is
exceeded.  This prevents simulataneous removal of points on
an outgoing and return leg, which can lead to gross corruption
of the route/track shape.

A test case that demonstrates the problem with the previous
algorithm is added.

position.cc
position.h
reference/track/position_track.gpx [new file with mode: 0644]
reference/track/position_track_filtered.gpx [new file with mode: 0644]
testo.d/position.test
xmldoc/filters/position.xml

index 009e01577940b72df2f589f4dfa40f85b08988e7..95994fe6a4f9b9565e58f525b1b6a68efa8f8fe6 100644 (file)
 #include <cmath>            // for fabs
 #include <cstdlib>          // for strtod
 
-#include <QtCore/QtGlobal>  // for foreach
+#include <QtCore/QList>     // for QList
+#include <QtCore/QtGlobal>  // for qAsConst, QAddConst<>::Type
 
 #include "defs.h"
 #include "filterdefs.h"
-#include "grtcirc.h"        // for RAD, gcdist, radtomiles
+#include "grtcirc.h"        // for RAD, gcdist, radtometers
 #include "position.h"
 
 #if FILTERS_ENABLED
 
 double PositionFilter::gc_distance(double lat1, double lon1, double lat2, double lon2)
 {
-  return gcdist(
-           RAD(lat1),
-           RAD(lon1),
-           RAD(lat2),
-           RAD(lon2)
-         );
+  return radtometers(gcdist(
+                       RAD(lat1),
+                       RAD(lon1),
+                       RAD(lat2),
+                       RAD(lon2)
+                     ));
 }
 
 /* tear through a waypoint queue, processing points by distance */
-void PositionFilter::position_runqueue(WaypointList* waypt_list, int nelems, int qtype)
+void PositionFilter::position_runqueue(WaypointList* waypt_list, int qtype)
 {
-  double dist, diff_time;
-  int i = 0, anyitem;
+  QList<WptRecord> qlist;
 
-  Waypoint** comp = (Waypoint**) xcalloc(nelems, sizeof(*comp));
-  int* qlist = (int*) xcalloc(nelems, sizeof(*qlist));
-
-  foreach (Waypoint* waypointp, *waypt_list) {
-    comp[i] = waypointp;
-    qlist[i] = 0;
-    i++;
+  for (const auto waypointp : qAsConst(*waypt_list)) {
+    qlist.append(WptRecord(waypointp));
   }
+  int nelems = qlist.size();
 
-  for (i = 0 ; i < nelems ; i++) {
-    anyitem = 0;
-
-    if (!qlist[i]) {
-      for (int j = i + 1 ; j < nelems ; j++) {
-        if (!qlist[j]) {
-          dist = gc_distance(comp[j]->latitude,
-                             comp[j]->longitude,
-                             comp[i]->latitude,
-                             comp[i]->longitude);
+  for (int i = 0 ; i < nelems ; ++i) {
+    bool something_deleted = false;
 
-          /* convert radians to integer feet */
-          dist = (int)(5280*radtomiles(dist));
-          diff_time = fabs(waypt_time(comp[i]) - waypt_time(comp[j]));
+    if (!qlist.at(i).deleted) {
+      for (int j = i + 1 ; j < nelems ; ++j) {
+        if (!qlist.at(j).deleted) {
+          double dist = gc_distance(qlist.at(j).wpt->latitude,
+                                    qlist.at(j).wpt->longitude,
+                                    qlist.at(i).wpt->latitude,
+                                    qlist.at(i).wpt->longitude);
 
           if (dist <= pos_dist) {
-            if (check_time && diff_time >= max_diff_time) {
-              continue;
+            if (check_time) {
+              double diff_time = fabs(waypt_time(qlist.at(i).wpt) - waypt_time(qlist.at(j).wpt));
+              if (diff_time >= max_diff_time) {
+                continue;
+              }
             }
 
-            qlist[j] = 1;
+            qlist[j].deleted = true;
             switch (qtype) {
             case wptdata:
-              waypt_del(comp[j]);
-              delete comp[j];
+              waypt_del(qlist.at(j).wpt);
+              delete qlist.at(j).wpt;
               break;
             case trkdata:
-              track_del_wpt(cur_rte, comp[j]);
-              delete comp[j];
+              track_del_wpt(cur_rte, qlist.at(j).wpt);
+              delete qlist.at(j).wpt;
               break;
             case rtedata:
-              route_del_wpt(cur_rte, comp[j]);
-              delete comp[j];
+              route_del_wpt(cur_rte, qlist.at(j).wpt);
+              delete qlist.at(j).wpt;
               break;
             default:
               break;
             }
-            anyitem = 1;
+            something_deleted = true;
+          } else {
+            // Unlike waypoints, routes and tracks are ordered paths.
+            // Don't eliminate points from the return path when the
+            // route or track loops back on itself.
+            if ((qtype == trkdata) || (qtype == rtedata)) {
+              break;
+            }
           }
         }
       }
 
-      if (anyitem && !!purge_duplicates) {
+      if (something_deleted && (purge_duplicates != nullptr)) {
         switch (qtype) {
         case wptdata:
-          waypt_del(comp[i]);
-          delete comp[i];
+          waypt_del(qlist.at(i).wpt);
+          delete qlist.at(i).wpt;
           break;
         case trkdata:
-          track_del_wpt(cur_rte, comp[i]);
-          delete comp[i];
+          track_del_wpt(cur_rte, qlist.at(i).wpt);
+          delete qlist.at(i).wpt;
           break;
         case rtedata:
-          route_del_wpt(cur_rte, comp[i]);
-          delete comp[i];
+          route_del_wpt(cur_rte, qlist.at(i).wpt);
+          delete qlist.at(i).wpt;
           break;
         default:
           break;
@@ -119,25 +121,15 @@ void PositionFilter::position_runqueue(WaypointList* waypt_list, int nelems, int
     }
   }
 
-  if (comp) {
-    xfree(comp);
-  }
-
-  if (qlist) {
-    xfree(qlist);
-  }
 }
 
 void PositionFilter::position_process_any_route(const route_head* rh, int type)
 {
-  int i = rh->rte_waypt_ct;
-
-  if (i) {
+  if (rh->rte_waypt_ct != 0) {
     cur_rte = const_cast<route_head*>(rh);
-    position_runqueue(&cur_rte->waypoint_list, i, type);
+    position_runqueue(&cur_rte->waypoint_list, type);
     cur_rte = nullptr;
   }
-
 }
 
 void PositionFilter::position_process_rte(const route_head* rh)
@@ -155,10 +147,8 @@ void PositionFilter::process()
   RteHdFunctor<PositionFilter> position_process_rte_f(this, &PositionFilter::position_process_rte);
   RteHdFunctor<PositionFilter> position_process_trk_f(this, &PositionFilter::position_process_trk);
 
-  int i = waypt_count();
-
-  if (i) {
-    position_runqueue(global_waypoint_list, i, wptdata);
+  if (waypt_count() != 0) {
+    position_runqueue(global_waypoint_list, wptdata);
   }
 
   route_disp_all(position_process_rte_f, nullptr, nullptr);
@@ -169,21 +159,21 @@ void PositionFilter::init()
 {
   char* fm;
 
-  pos_dist = 0;
-  max_diff_time = 0;
-  check_time = 0;
+  pos_dist = 0.0;
+  max_diff_time = 0.0;
+  check_time = false;
 
-  if (distopt) {
+  if (distopt != nullptr) {
     pos_dist = strtod(distopt, &fm);
 
-    if ((*fm == 'm') || (*fm == 'M')) {
-      /* distance is meters */
-      pos_dist *= 3.2802;
+    if (!((*fm == 'm') || (*fm == 'M'))) {
+      /* distance is feet */
+      pos_dist = FEET_TO_METERS(pos_dist);
     }
   }
 
-  if (timeopt) {
-    check_time = 1;
+  if (timeopt != nullptr) {
+    check_time = true;
     max_diff_time = strtod(timeopt, &fm);
   }
 }
index e7eb69348f28fb3eb29725f2c88093c5a72f8f66..4637892d09d7d47fbbb9514d5f08d377da9389a5 100644 (file)
@@ -45,11 +45,7 @@ private:
   char* distopt = nullptr;
   char* timeopt = nullptr;
   char* purge_duplicates = nullptr;
-  int check_time;
-
-  typedef struct {
-    double distance;
-  } extra_data;
+  bool check_time;
 
   arglist_t args[4] = {
     {
@@ -68,8 +64,17 @@ private:
     ARG_TERMINATOR
   };
 
+  class WptRecord
+  {
+  public:
+    Waypoint* wpt{nullptr};
+    bool deleted{false};
+
+    explicit WptRecord(Waypoint* w) : wpt(w) {}
+  };
+
   double gc_distance(double lat1, double lon1, double lat2, double lon2);
-  void position_runqueue(WaypointList* waypt_list, int nelems, int qtype);
+  void position_runqueue(WaypointList* waypt_list, int qtype);
   void position_process_any_route(const route_head* rh, int type);
   void position_process_rte(const route_head* rh);
   void position_process_trk(const route_head* rh);
diff --git a/reference/track/position_track.gpx b/reference/track/position_track.gpx
new file mode 100644 (file)
index 0000000..5b8d5f3
--- /dev/null
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gpx version="1.0" creator="GPSBabel - http://www.gpsbabel.org" xmlns="http://www.topografix.com/GPX/1/0">
+  <time>2019-07-06T15:46:06.559Z</time>
+  <bounds minlat="40.008761985" minlon="-105.207110341" maxlat="40.009199023" maxlon="-105.205790238"/>
+  <trk>
+    <name>Untitled Path</name>
+    <trkseg>
+      <trkpt lat="40.009054849" lon="-105.206004844"/>
+      <trkpt lat="40.009031242" lon="-105.206070277"/>
+      <trkpt lat="40.009003650" lon="-105.206144881"/>
+      <trkpt lat="40.008976867" lon="-105.206225622"/>
+      <trkpt lat="40.008941848" lon="-105.206316939"/>
+      <trkpt lat="40.008909765" lon="-105.206391276"/>
+      <trkpt lat="40.008883566" lon="-105.206475630"/>
+      <trkpt lat="40.008850838" lon="-105.206558580"/>
+      <trkpt lat="40.008820497" lon="-105.206640987"/>
+      <trkpt lat="40.008794757" lon="-105.206702329"/>
+      <trkpt lat="40.008761985" lon="-105.206788064"/>
+      <trkpt lat="40.008805279" lon="-105.206815339"/>
+      <trkpt lat="40.008859906" lon="-105.206845941"/>
+      <trkpt lat="40.008905939" lon="-105.206879224"/>
+      <trkpt lat="40.008945815" lon="-105.206917917"/>
+      <trkpt lat="40.008989205" lon="-105.206964005"/>
+      <trkpt lat="40.009030426" lon="-105.207004804"/>
+      <trkpt lat="40.009073806" lon="-105.207053719"/>
+      <trkpt lat="40.009121469" lon="-105.207110341"/>
+      <trkpt lat="40.009164218" lon="-105.207035615"/>
+      <trkpt lat="40.009118062" lon="-105.206978787"/>
+      <trkpt lat="40.009072029" lon="-105.206928441"/>
+      <trkpt lat="40.009030957" lon="-105.206889679"/>
+      <trkpt lat="40.008992964" lon="-105.206852408"/>
+      <trkpt lat="40.008921748" lon="-105.206773246"/>
+      <trkpt lat="40.008886986" lon="-105.206698496"/>
+      <trkpt lat="40.008905642" lon="-105.206619702"/>
+      <trkpt lat="40.008927841" lon="-105.206560216"/>
+      <trkpt lat="40.008951025" lon="-105.206500804"/>
+      <trkpt lat="40.008974216" lon="-105.206446800"/>
+      <trkpt lat="40.008992342" lon="-105.206380934"/>
+      <trkpt lat="40.009020531" lon="-105.206315106"/>
+      <trkpt lat="40.009042694" lon="-105.206261158"/>
+      <trkpt lat="40.009064774" lon="-105.206192784"/>
+      <trkpt lat="40.009085925" lon="-105.206134925"/>
+      <trkpt lat="40.009109989" lon="-105.206066651"/>
+      <trkpt lat="40.009137114" lon="-105.205995744"/>
+      <trkpt lat="40.009161137" lon="-105.205930216"/>
+      <trkpt lat="40.009181255" lon="-105.205863237"/>
+      <trkpt lat="40.009199023" lon="-105.205790238"/>
+    </trkseg>
+  </trk>
+</gpx>
diff --git a/reference/track/position_track_filtered.gpx b/reference/track/position_track_filtered.gpx
new file mode 100644 (file)
index 0000000..f895c62
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gpx version="1.0" creator="GPSBabel - http://www.gpsbabel.org" xmlns="http://www.topografix.com/GPX/1/0">
+  <time>1970-01-01T00:00:00Z</time>
+  <bounds minlat="40.008761985" minlon="-105.207110341" maxlat="40.009181255" maxlon="-105.205863237"/>
+  <trk>
+    <name>Untitled Path</name>
+    <trkseg>
+      <trkpt lat="40.009054849" lon="-105.206004844"/>
+      <trkpt lat="40.009003650" lon="-105.206144881"/>
+      <trkpt lat="40.008941848" lon="-105.206316939"/>
+      <trkpt lat="40.008883566" lon="-105.206475630"/>
+      <trkpt lat="40.008820497" lon="-105.206640987"/>
+      <trkpt lat="40.008761985" lon="-105.206788064"/>
+      <trkpt lat="40.008905939" lon="-105.206879224"/>
+      <trkpt lat="40.009030426" lon="-105.207004804"/>
+      <trkpt lat="40.009121469" lon="-105.207110341"/>
+      <trkpt lat="40.009072029" lon="-105.206928441"/>
+      <trkpt lat="40.008921748" lon="-105.206773246"/>
+      <trkpt lat="40.008905642" lon="-105.206619702"/>
+      <trkpt lat="40.008974216" lon="-105.206446800"/>
+      <trkpt lat="40.009020531" lon="-105.206315106"/>
+      <trkpt lat="40.009085925" lon="-105.206134925"/>
+      <trkpt lat="40.009137114" lon="-105.205995744"/>
+      <trkpt lat="40.009181255" lon="-105.205863237"/>
+    </trkseg>
+  </trk>
+</gpx>
index 2669d8d7779e6ddf03a587f9641a7714e4670fe1..bd59ae995dad367cc5c1985b6fc45d8bd03372c7 100644 (file)
@@ -8,3 +8,7 @@ gpsbabel -i geo -f ${REFERENCE}/../geocaching.loc -o csv -F ${TMPDIR}/filterpos.
 gpsbabel -i geo -f ${REFERENCE}/../geocaching.loc -f ${REFERENCE}/../geocaching.loc -x position,distance=5f \
                -o csv -F ${TMPDIR}/filterpos.csv2
 sort_and_compare ${TMPDIR}/filterpos.csv1 ${TMPDIR}/filterpos.csv2
+
+# check a track that loops back with a return leg adjacent to an outgoing leg.
+gpsbabel -i gpx -f reference/track/position_track.gpx -x position,distance=12m -o gpx -F ${TMPDIR}/position_track_filtered.gpx
+compare ${REFERENCE}/track/position_track_filtered.gpx ${TMPDIR}/position_track_filtered.gpx
index 01df222c717121f98cd5ada5f8ee832e2c312572..c6b957179107e08266f924423b9624f63eb8e87f 100644 (file)
@@ -1,7 +1,7 @@
 <para> 
-This filter removes points based on their proximity to each other.  A 
-point is removed if it is within the specified distance of a point that 
-has come before.
+This filter removes points based on their proximity to each other.
+For waypoints a point is removed if it is within the specified distance of a preceeding point.
+For routes and tracks consecutive points are removed until the distance between the bracketing points is greater than the specified distance.
 </para>
 
 <example id="posn_to_suppress_close_points">